package com.herr.springboot.mq.rabbitmq.ffmpeg;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;


/**
 * 基于FFmpeg内核来编解码音视频信息；
 * 使用前需手动在运行环境中安装FFmpeg运行程序，然后正确设置FFmpeg运行路径后MediaUtil.java才能正常调用到FFmpeg程序去处理音视频；
 *
 * Author: dreamer-1
 * 
 * version: 1.0
 *
 */

@Component
public class FFmpegUtil {

	/**
	 * 可以处理的视频格式
	 */
	public final static String[] VIDEO_TYPE = { "MP4", "WMV" };
	/**
	 * 可以处理的图片格式
	 */
	public final static String[] IMAGE_TYPE = { "JPG", "JPEG", "PNG", "GIF" };
	/**
	 * 可以处理的音频格式
	 */
	public final static String[] AUDIO_TYPE = { "AAC" };
	/**
	 * FFmpeg程序执行路径 当前系统安装好ffmpeg程序并配置好相应的环境变量后，值为ffmpeg可执行程序文件在实际系统中的绝对路径
	 */
	private static String FFMPEG_PATH;

	@Value("${ffmpeg.server.path}")
	public void setFfmpegPath(String FFMPEG_PATH){
		this.FFMPEG_PATH = FFMPEG_PATH;
	}

	/**
	 * 视频时长正则匹配式 用于解析视频及音频的时长等信息时使用；
	 *
	 * (.*?)表示：匹配任何除\r\n之外的任何0或多个字符，非贪婪模式
	 *
	 */
	private static String durationRegex = "Duration: (\\d*?):(\\d*?):(\\d*?)\\.(\\d*?), start: (.*?), bitrate: (\\d*) kb\\/s.*";
	private static Pattern durationPattern;
	/**
	 * 视频流信息正则匹配式 用于解析视频详细信息时使用；
	 */
	private static String videoStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Video: (\\S*\\S$?)[^\\,]*, (.*?), (\\d*)x(\\d*)[^\\,]*, (\\d*) kb\\/s, (\\d*[\\.]??\\d*) fps";
	private static Pattern videoStreamPattern;

	/**
	 * 视频流信息正则匹配式 用于解析视频详细信息时使用；
	 */
	private static String videoRegex = "Video: (\\S*\\S$?)[^\\,]*, (.*?), (\\d*)x(\\d*)[^\\,]*";
	private static Pattern videoPattern;

	/**
	 * 视频流信息正则匹配式 用于解析视频拍摄时间；
	 */
	private static String videoRegexShootTime = "creation_time   : (\\d*-\\d*-\\d*)T";
	private static Pattern videoShootTimePattern;

	/**
	 * 音频流信息正则匹配式 用于解析音频详细信息时使用；
	 */
	private static String musicStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Audio: (\\S*\\S$?)(.*), (.*?) Hz, (.*?), (.*?), (\\d*) kb\\/s";;
	private static Pattern musicStreamPattern;

	/**
	 * 静态初始化时先加载好用于音视频解析的正则匹配式
	 */
	static {
		durationPattern = Pattern.compile(durationRegex);
		videoStreamPattern = Pattern.compile(videoStreamRegex);
		musicStreamPattern = Pattern.compile(musicStreamRegex);
		videoPattern = Pattern.compile(videoRegex);
		videoShootTimePattern = Pattern.compile(videoRegexShootTime);
	}

	/**
	 * 获取当前多媒体处理工具内的ffmpeg的执行路径
	 * 
	 * @return
	 */
	public static String getFFmpegPath() {
		return FFMPEG_PATH;
	}

	private static final Logger log = LoggerFactory.getLogger("file");

	/**
	 * 设置当前多媒体工具内的ffmpeg的执行路径
	 * 
	 * @param ffmpeg_path ffmpeg可执行程序在实际系统中的绝对路径
	 * @return
	 */
	public static boolean setFFmpegPath(String ffmpeg_path) {
		if (StringUtils.isEmpty(ffmpeg_path)) {
			log.error("--- 设置ffmpeg执行路径失败，因为传入的ffmpeg可执行程序路径为空！ ---");
			return false;
		}
		File ffmpegFile = new File(ffmpeg_path);
		if (!ffmpegFile.exists()) {
			log.error("--- 设置ffmpeg执行路径失败，因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在！ ---");
			return false;
		}
		FFMPEG_PATH = ffmpeg_path;
//		log.info("--- 设置ffmpeg执行路径成功 --- 当前ffmpeg可执行程序路径为： " + ffmpeg_path);
		return true;
	}

	/**
	 * 执行FFmpeg命令
	 * 
	 * @param commonds 要执行的FFmpeg命令
	 * @return FFmpeg程序在执行命令过程中产生的各信息，执行出错时返回null
	 */
	public static String executeCommand(List<String> commonds) {
		if (CollectionUtils.isEmpty(commonds)) {
			log.error("--- 指令执行失败，因为要执行的FFmpeg指令为空！ ---");
			return null;
		}
		LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
		ffmpegCmds.addFirst(FFMPEG_PATH+"/ffmpeg"); // 设置ffmpeg程序所在路径
//		ffmpegCmds.addFirst("/Program Files/ffmpeg-20200729-cbb6ba2-win64-static/bin"+"/ffmpeg"); // 设置ffmpeg程序所在路径
		log.error("--- 待执行的FFmpeg指令为：---" + ffmpegCmds);
		log.info("---addFirst---"+ FFMPEG_PATH+"/ffmpeg");

		Runtime runtime = Runtime.getRuntime();
		Process ffmpeg = null;
		try {
			// 执行ffmpeg指令
			ProcessBuilder builder = new ProcessBuilder();
			builder.command(ffmpegCmds);
			ffmpeg = builder.start();
			log.info("--- 开始执行FFmpeg指令：--- 执行线程名：" + builder.toString());
			log.info("--- 已启动FFmpeg进程 --- 进程名： " + ffmpeg.toString());

			log.info("begin set process killer");
			ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
			 //JVM退出时，先通过钩子关闭FFmepg进程
			runtime.addShutdownHook(ffmpegKiller);

			// 取出输出流和错误流的信息
			// 注意：必须要取出ffmpeg在执行命令过程中产生的输出信息，如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时，线程就回阻塞住
			PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
			PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
			errorStream.start();
			inputStream.start();
			// 等待ffmpeg命令执行完
			ffmpeg.waitFor();

			// 获取执行结果字符串
			String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();

			// 输出执行的命令信息
			String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
			//String resultStr = StringUtils.isEmpty(result) ? "【异常】" : "正常";
			log.info("--- 已执行的FFmepg命令： ---" + cmdStr + " 已执行完毕,执行结果： " + result );
			//log.error("==============result===="+ result);
			return result;

		} catch (Exception e) {
			log.error("--- FFmpeg命令执行出错！ --- 出错信息： " + e.getMessage());
			log.info("--- FFmpeg命令执行出错！ --- 出错信息： " + e.getMessage());
			return null;

		} finally {
			if (null != ffmpeg) {
				//关掉ffmpeg
				try {
					ffmpeg.getInputStream().close();
					ffmpeg.getOutputStream().close();
					ffmpeg.getErrorStream().close();
					ffmpeg.destroy();
					log.info("close ffmpeg stream");
				}catch (Exception e){
					e.printStackTrace();
				}

			}
		}
	}

	/**
	 * 在程序退出前结束已有的FFmpeg进程
	 */
	private static class ProcessKiller extends Thread {
		private Process process;

		public ProcessKiller(Process process) {
			this.process = process;
		}

		@Override
		public void run() {
			if (this.process!=null && this.process.isAlive()){
				this.process.destroy();
				log.info("--- 已销毁FFmpeg进程 --- 进程名： " + process.toString());
			}

		}
	}

	/**
	 * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
	 */
	static class PrintStream extends Thread {
		InputStream inputStream = null;
		BufferedReader bufferedReader = null;
		StringBuffer stringBuffer = new StringBuffer();

		public PrintStream(InputStream inputStream) {
			this.inputStream = inputStream;
		}

		@Override
		public void run() {
			try {
				if (null == inputStream) {
					log.error("--- 读取输出流出错！因为当前输出流为空！---");
				}
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
				String line = null;
				while ((line = bufferedReader.readLine()) != null) {
//					log.info(line);
					stringBuffer.append(line);
				}
			} catch (Exception e) {
				log.error("--- 读取输入流出错了！--- 错误信息：" + e.getMessage());
			} finally {
				try {
					if (null != bufferedReader) {
						bufferedReader.close();
					}
					if (null != inputStream) {
						inputStream.close();
					}
				} catch (IOException e) {
					log.error("--- 调用PrintStream读取输出流后，关闭流时出错！---");
				}
			}
		}
	}

}